class UpdaterManager {
  private _rendersOrder: {
    update: Updater;
    fps?: number;
    interval: number;
    renderLast: number;
    last: number;
  }[] = [];
  private _fps?: number;
  private _isRunning = false;
  static DEFAULT_FPS = 60;
  static DEFAULT_FPS_KEY = 'dFps';

  get dFps() {
    return this._fps === undefined ? UpdaterManager.DEFAULT_FPS : this._fps;
  }

  set dFps(newFps: number) {
    this._fps = newFps;
    this._rendersOrder.forEach((render) => {
      if (render.fps === undefined) {
        render.interval = 1000 / this.dFps;
      }
    });
  }

  start(render: Updater) {
    if (!this._isRunning) {
      this._isRunning = true;
      this._update();
    }
    this.changeRender(render);
  }

  stop(render: Updater) {
    this._removeRender(render);
    if (this._rendersOrder.length === 0) {
      this._isRunning = false;
    }
  }

  private _removeRender(render: Updater) {
    const delIndex = this._rendersOrder.findIndex((item) => item.update === render) ?? -1;
    if (delIndex !== -1) {
      this._rendersOrder.splice(delIndex, 1);
    }
  }

  changeRender(render: Updater) {
    const fps = render.fps === undefined ? this.dFps : render.fps;
    this._removeRender(render);
    const now = performance.now();

    this._rendersOrder.push({
      update: render,
      fps: render.fps,
      interval: 1000 / fps,
      renderLast: now,
      last: now
    });
    this._rendersOrder.sort((a, b) => a.update.order - b.update.order);
  }

  private _update() {
    if (!this._isRunning) {
      return;
    }

    const now = performance.now();

    this._rendersOrder.forEach((render) => {
      const delta = now - render.last;
      if (delta >= render.interval) {
        const deltaTime = Math.min(now - render.renderLast, 100) / 1000;
        render.update.update(deltaTime);
        render.renderLast = now;
        render.last = now - (delta % render.interval);
      }
    });

    requestAnimationFrame(() => {
      this._update();
    });
  }
}

const updaterManager = new UpdaterManager();

export type UpdateParams = {
  order?: number;
  fps?: number;
  handler?: (deltaTime: number, duration: number) => void;
};

export class Updater {
  private _order: number;
  private _fps?: number;
  private _handler?: (deltaTime: number, duration: number) => void;
  private _isRunning = false;
  private _startTime = -1;
  private _duration = 0;
  private _updaterManager: UpdaterManager;

  constructor(params: UpdateParams = {}) {
    this._order = params.order ?? -1;
    this._fps = params.fps;
    this._handler = params.handler;
    this._updaterManager = updaterManager;
  }

  private _setEnd() {
    if (this._isRunning) {
      this._updaterManager.changeRender(this);
    }
  }

  get isRunning() {
    return this._isRunning;
  }

  get order() {
    return this._order;
  }

  set order(newOrder: number) {
    this._order = newOrder;
    this._setEnd();
  }

  get fps() {
    return this._fps;
  }

  set fps(newFps: number | undefined) {
    this._fps = newFps;
    this._setEnd();
  }

  getDFps() {
    return this._updaterManager.dFps;
  }

  setDFps(newFps: number | undefined) {
    if (newFps === undefined) {
      this._updaterManager.dFps = UpdaterManager.DEFAULT_FPS;
    } else {
      this._updaterManager.dFps = newFps;
    }
  }

  get startTime() {
    return this._startTime;
  }

  get duration() {
    return this._duration;
  }

  update(deltaTime: number) {
    const nowTime = performance.now();
    const duration = nowTime - this._startTime;
    this._duration = duration;
    this._handler?.(deltaTime, duration);
  }

  static start(params?: UpdateParams) {
    const temp = new Updater(params);
    temp.start();
    return temp;
  }

  start() {
    if (!this._isRunning) {
      this._isRunning = true;
      this._startTime = performance.now();
      this._updaterManager.start(this);
    }
  }

  stop() {
    if (this._isRunning) {
      this._isRunning = false;
      this._startTime = -1;
      this._updaterManager.stop(this);
    }
  }
}
